package app

import (
	"cade/src/core/log"
	"sync"
	"sync/atomic"
)

// ============================================================================

var PlayerMgr = &plrmgr_t{
	plrs_by_id:   make(map[string]*Player),
	plrs_by_name: make(map[string]*Player),
	plrs_online:  make(map[uint64]*Player),
}

// ============================================================================

type plrmgr_t struct {
	plrs_by_id   map[string]*Player // loaded players by id
	plrs_by_name map[string]*Player // loaded players by name
	plrs_online  map[uint64]*Player // online players by sid

	num_online int32 // online player number

	locker_online sync.RWMutex

	wg sync.WaitGroup
}

// ============================================================================

func (self *plrmgr_t) Open() {
}

func (self *plrmgr_t) Close() {
	// stop all players' goroutines
	self.locker_online.RLock()
	arr := make([]*Player, 0, len(self.plrs_by_id))
	for _, plr := range self.plrs_by_id {
		arr = append(arr, plr)
	}
	self.locker_online.RUnlock()

	for _, plr := range arr {
		plr.stop()
	}

	// wait
	self.wg.Wait()

	log.Info("ALL players are stopped")
}

func (self *plrmgr_t) FindPlayer(uid string) *Player {
	// lock
	self.locker_online.Lock()
	defer self.locker_online.Unlock()

	return self.plrs_by_id[uid]
}

func (self *plrmgr_t) FindPlayerByName(name string) *Player {
	// lock
	self.locker_online.Lock()
	defer self.locker_online.Unlock()

	return self.plrs_by_name[name]
}

func (self *plrmgr_t) FindIPlayer(uid string) interface{} {
	plr := self.FindPlayer(uid)
	if plr == nil {
		return nil
	} else {
		return plr
	}
}

func (self *plrmgr_t) FindIPlayerByName(name string) interface{} {
	plr := self.FindPlayerByName(name)
	if plr == nil {
		return nil
	} else {
		return plr
	}
}

func (self *plrmgr_t) SetOnline(plr *Player, sid uint64) {
	self.locker_online.Lock()
	defer self.locker_online.Unlock()

	// add new one
	plr.sid = sid
	self.plrs_online[sid] = plr
	self.plrs_by_id[plr.user.Id] = plr
	self.plrs_by_name[plr.user.Name] = plr

	// event: online
	plr.Push(func() {
		plr.OnOnline()
	})

	// run player
	self.wg.Add(1)
	plr.run(self.wg.Done)

	// count
	atomic.AddInt32(&self.num_online, 1)
}

func (self *plrmgr_t) SetOffline(plr *Player, shutdown bool) {
	self.locker_online.Lock()
	defer self.locker_online.Unlock()

	plr.sid = 0
	delete(self.plrs_online, plr.sid)
	delete(self.plrs_by_id, plr.user.Id)
	delete(self.plrs_by_name, plr.user.Name)

	// event: offline
	plr.Push(func() {
		plr.OnOffline(shutdown)
		plr.stop()
	})

	// count
	atomic.AddInt32(&self.num_online, -1)
}

func (self *plrmgr_t) OfflinePlayers(gateid int32) {
	// find
	self.locker_online.RLock()
	arr := make([]*Player, 0, len(self.plrs_online))
	if gateid == 0 {
		// all players
		for _, plr := range self.plrs_online {
			arr = append(arr, plr)
		}
	} else {
		// players from gateid
		for _, plr := range self.plrs_online {
			if sid_to_gateid(plr.sid) == gateid {
				arr = append(arr, plr)
			}
		}
	}
	defer self.locker_online.RUnlock()

	// then, offline
	for _, plr := range arr {
		plr.Push(func() {
			self.SetOffline(plr, true)
		})
	}
}

func (self *plrmgr_t) FindPlayerBySid(sid uint64) *Player {
	self.locker_online.RLock()
	defer self.locker_online.RUnlock()

	return self.plrs_online[sid]
}

func (self *plrmgr_t) ForEachOnlineIPlayer(f func(plr interface{})) {
	self.locker_online.RLock()
	defer self.locker_online.RUnlock()

	for _, plr := range self.plrs_online {
		f(plr)
	}
}

func (self *plrmgr_t) NumOnline() int32 {
	return atomic.LoadInt32(&self.num_online)
}
